/*
 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.oracle.truffle.api.bytecode;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.bytecode.BytecodeEngineData.DescriptorData;
import com.oracle.truffle.api.bytecode.serialization.BytecodeDeserializer;
import com.oracle.truffle.api.bytecode.serialization.BytecodeSerializer;
import com.oracle.truffle.api.nodes.RootNode;

/**
 * Describes the bytecode interpreter generated for a specific {@link BytecodeRootNode} class and
 * provides the management API for that interpreter.
 * <p>
 * A generated bytecode interpreter exposes a public static {@code BYTECODE} field of the generated
 * {@code *Gen.Bytecode} class. It is recommended to not refer to the {@link BytecodeDescriptor}
 * directly and instead use the generated {@code *Gen.Bytecode} class to avoid the rather verbose
 * generic type. That field is a concrete subclass of {@link BytecodeDescriptor}, and acts as the
 * central entry point for:
 *
 * <ul>
 * <li><strong>Creating executable bytecode:</strong>
 * {@link #create(TruffleLanguage, BytecodeConfig, BytecodeParser)} builds one or more
 * {@link BytecodeRootNode} instances by driving a {@link BytecodeBuilder}.
 *
 * <li><strong>Serialization support:</strong> {@link #serialize} and {@link #deserialize} define
 * the contract for turning parsed bytecode into a byte stream and restoring it. Generated
 * interpreters that were built with {@code enableSerialization=true} implement these methods. The
 * default implementation throws {@link UnsupportedOperationException}.
 *
 * <li><strong>Describing the instruction set:</strong> {@link #getInstructionDescriptors()} and
 * {@link #getInstructionDescriptor(int)} expose all instructions, including normal language
 * operations and injected instrumentation instructions. The descriptor can also produce a human
 * readable dump via {@link #dump()}.
 *
 * <li><strong>Per-language runtime configuration:</strong>
 * {@link #update(TruffleLanguage, BytecodeConfig)} applies configuration (for example enabling
 * instrumentations) to all bytecode roots that were already created for the given language, and to
 * any future roots.</li>
 *
 * <li><strong>Instruction tracing:</strong>
 * {@link #addInstructionTracer(TruffleLanguage, InstructionTracer)} and
 * {@link #removeInstructionTracer(TruffleLanguage, InstructionTracer)} install and remove
 * {@link InstructionTracer}s for all roots created for a given language. After a tracer is
 * installed, the interpreter automatically injects tracing instructions (for example
 * {@code trace.instruction}) and invokes the tracer callback on every executed instruction.
 * </ul>
 *
 * The descriptor also exposes metadata about the interpreter itself: {@link #getGeneratedClass()}
 * returns the generated root node class that implements execution, and
 * {@link #getSpecificationClass()} identifies the original user-declared {@code @GenerateBytecode}
 * root node (when available). {@link #getLanguageClass()} identifies the owning
 * {@link TruffleLanguage}.
 * <p>
 * In short, {@code BytecodeDescriptor} is both: <em>(1)</em> a static description of the generated
 * bytecode interpreter (instruction set, generated classes, language binding), and <em>(2)</em> the
 * runtime control surface for all bytecode roots created from that interpreter in a given language
 * context (creation, tracing, instrumentation, serialization).
 * <p>
 * <h2>Usage example</h2>
 *
 * <pre>{@code
 * // Assume you have a language MyLanguage and a bytecode root node
 * // declared as:
 * //
 * //   @GenerateBytecode(languageClass = MyLanguage.class)
 * //   abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode { ... }
 * //
 * // The annotation processor generates MyBytecodeRootNodeGen and a nested
 * // MyBytecodeRootNodeGen.Bytecode class with a public static BYTECODE field:
 * //
 * //   public static final MyBytecodeRootNodeGen.Bytecode BYTECODE;
 * //
 * // The BYTECODE field is a concrete BytecodeDescriptor.
 *
 * MyLanguage language = ...; // instance of the Truffle language
 *
 * // Create a new bytecode root node by emitting bytecode through the builder.
 * // The parser lambda drives the builder to generate the bytecode body.
 * MyBytecodeRootNode root = MyBytecodeRootNodeGen.BYTECODE
 *     .create(language, BytecodeConfig.DEFAULT, (builder) -> {
 *         builder.beginRoot();
 *         builder.beginReturn();
 *         builder.emitLoadArgument(0);   // return the first argument
 *         builder.endReturn();
 *         builder.endRoot();
 *     })
 *     .getNode(0); // obtain the first created root node
 *
 * // Execute the generated root node through its call target.
 * Object result = root.getCallTarget().call(42);
 * }</pre>
 *
 * @param <R> the {@link #getSpecificationClass() specification class}, which is both a
 *            {@link RootNode} and a {@link BytecodeRootNode}
 * @param <L> the Truffle language type
 * @param <B> the builder type used to emit bytecode for this interpreter
 * @since 25.1
 */
public abstract class BytecodeDescriptor<R extends RootNode & BytecodeRootNode, L extends TruffleLanguage<?>, B extends BytecodeBuilder> {

    private static volatile boolean engineDescriptorLookupEnabled;
    private volatile boolean descriptorLookupEnabled;

    protected BytecodeDescriptor(Object token) {
        BytecodeRootNodes.checkToken(token);
    }

    /**
     * Returns the specification class for this descriptor. This is the user defined
     * {@link RootNode} subclass that was annotated to generate this bytecode interpreter.
     *
     * @return the specification class, never <code>null</code>.
     * @since 25.1
     */
    public abstract Class<R> getSpecificationClass();

    /**
     * Returns the generated root node class that implements the bytecode interpreter. This is the
     * concrete {@link RootNode} subclass created by the bytecode generator.
     *
     * @return the generated root node class, never <code>null</code>
     * @since 25.1
     */
    public abstract Class<? extends R> getGeneratedClass();

    /**
     * Returns the {@link TruffleLanguage} class that this descriptor is associated with, never
     * <code>null</code>.
     *
     * @see GenerateBytecode#languageClass()
     * @since 25.1
     */
    public abstract Class<L> getLanguageClass();

    /**
     * Returns the {@link InstructionDescriptor} for the given operation code, or <code>null</code>
     * if the code is not defined by this interpreter.
     *
     * @param operationCode the numeric operation code of the instruction
     * @see InstructionDescriptor#getOperationCode()
     * @see Instruction#getOperationCode()
     * @since 25.1
     */
    public abstract InstructionDescriptor getInstructionDescriptor(int operationCode);

    /**
     * Returns an immutable list of all instruction descriptors known to this interpreter. The list
     * describes every instruction kind that can be executed by nodes generated from this
     * interpreter.
     *
     * @since 25.1
     */
    public abstract List<InstructionDescriptor> getInstructionDescriptors();

    /**
     * Casts a {@link RootNode} to the specification root node type {@code R}. This also handles
     * {@link ContinuationRootNode} if yield is enabled. Returns <code>null</code> if the given root
     * is not compatible with this descriptor.
     *
     * @param rootNode a root node, must not be <code>null</code>
     * @since 25.1
     */
    public abstract R cast(RootNode rootNode);

    /**
     * Casts a {@link CallTarget} to the specification root node type {@code R}. Returns
     * <code>null</code> if the given root is not compatible with this descriptor.
     *
     * @param target a call target, must not be <code>null</code>
     * @since 25.1
     */
    public final R cast(CallTarget target) {
        Objects.requireNonNull(target);
        if (target instanceof RootCallTarget r) {
            return cast(r.getRootNode());
        }
        return null;
    }

    /**
     * Creates a new {@link BytecodeConfig.Builder} initialized with defaults that are valid for
     * this descriptor.
     *
     * @return a new mutable configuration builder
     * @since 25.1
     */
    public abstract BytecodeConfig.Builder newConfigBuilder();

    /**
     * Creates one or more bytecode root nodes. This is the entrypoint for creating new {@link R}
     * instances.
     * <p>
     * The {@code parser} is invoked to emit builder calls on a {@link BytecodeBuilder} which is
     * then materialized into executable bytecode. The returned {@link BytecodeRootNodes} contains
     * the created root nodes, for example entry points and helper nodes.
     *
     * @param language the language instance
     * @param config the bytecode configuration, for example whether to include source information
     * @param parser the parser that drives the {@link BytecodeBuilder}
     * @return the created bytecode root nodes
     * @since 25.1
     */
    public abstract BytecodeRootNodes<R> create(L language, BytecodeConfig config, BytecodeParser<B> parser);

    /**
     * Serializes the bytecode nodes parsed by the {@code parser}. All metadata (e.g., source info)
     * is serialized (even if it has not yet been parsed).
     * <p>
     * Unlike the {@link BytecodeRootNodes#serialize} instance method, which replays builder calls
     * that were already validated during the original bytecode parse, this method does
     * <strong>not</strong> validate the builder calls performed by the {@code parser}. Validation
     * will happen (as usual) when the bytes are deserialized.
     * <p>
     * Additionally, this method cannot serialize field values that get set outside of the parser,
     * unlike the {@link BytecodeRootNodes#serialize} instance method, which has access to the
     * instances being serialized.
     *
     * @param buffer the buffer to write the byte output to.
     * @param callback the language-specific serializer for constants in the bytecode.
     * @param parser the parser.
     * @since 25.1
     */
    @SuppressWarnings("unused")
    @TruffleBoundary
    public void serialize(DataOutput buffer, BytecodeSerializer callback, BytecodeParser<B> parser) throws IOException {
        throw new UnsupportedOperationException("The generated bytecode interpreter does not support serialization. Enable serialization using @GenerateBytecode(enableSerialization=true).");
    }

    /**
     * Deserializes a byte sequence to bytecode nodes. The bytes must have been produced by a
     * previous call to {@link #serialize}.
     *
     * @param language the language instance.
     * @param config indicates whether to deserialize metadata (e.g., source information).
     * @param input A function that supplies the bytes to deserialize. This supplier must produce a
     *            new {@link DataInput} each time, since the bytes may be processed multiple times
     *            for reparsing.
     * @param callback The language-specific deserializer for constants in the bytecode. This
     *            callback must perform the inverse of the callback that was used to
     *            {@link #serialize} the nodes to bytes.
     * @since 25.1
     */
    @SuppressWarnings("unused")
    @TruffleBoundary
    public BytecodeRootNodes<R> deserialize(L language, BytecodeConfig config, Supplier<DataInput> input, BytecodeDeserializer callback) throws IOException {
        throw new UnsupportedOperationException("The generated bytecode interpreter does not support serialization. Enable serialization using @GenerateBytecode(enableSerialization=true).");
    }

    /**
     * Registers an {@link InstructionTracer} for all bytecode root nodes created from this
     * descriptor in the given language.
     * <p>
     * After a tracer is installed, all existing and future {@link BytecodeRootNode} instances
     * associated with this descriptor and {@code language} will start invoking the tracer's
     * callback before executing each instruction.
     * <p>
     * Installing a tracer is intentionally invasive and may be expensive:
     * <ul>
     * <li>All affected bytecode roots may need to be invalidated and re-parsed so that tracing
     * hooks (for example {@code trace.instruction}) can be inserted.</li>
     * <li>The change applies process-wide to that language instance, not just to a single root
     * node.</li>
     * </ul>
     * Tracing itself runs on the language execution thread and may be called at very high
     * frequency.
     * <p>
     * If this bytecode interpreter was generated without instruction tracing support (for example
     * using {@code @GenerateBytecode(enableInstructionTracing = false)}), this method throws
     * {@link UnsupportedOperationException}.
     *
     * @param language the language instance whose bytecode roots should start reporting
     * @param tracer the tracer to install
     * @throws IllegalArgumentException if {@code tracer} is exclusive to a different
     *             {@link BytecodeDescriptor}
     * @throws UnsupportedOperationException if instruction tracing is not enabled for this
     *             descriptor
     * @since 25.1
     */
    public void addInstructionTracer(L language, InstructionTracer tracer) {
        Objects.requireNonNull(language);
        Objects.requireNonNull(tracer);
        getDescriptorData(language).addInstructionTracer(tracer);
    }

    /**
     * Unregisters a previously installed {@link InstructionTracer} for the given language.
     * <p>
     * After removal, bytecode roots created from this descriptor for {@code language} will stop
     * invoking the tracer's callback. Future roots created for the same language will also not
     * attach the tracer. The {@code tracer} argument must be the same tracer instance that was
     * passed to {@link #addInstructionTracer(TruffleLanguage, InstructionTracer)}.
     * <p>
     * Note that currently when all instruction tracers were removed, the trace instructions remain
     * in the bytecode. This may be improved in the future.
     * <p>
     * If this bytecode interpreter was generated without instruction tracing support (for example
     * using {@code @GenerateBytecode(enableInstructionTracing = false)}), this method throws
     * {@link UnsupportedOperationException}.
     *
     * @param language the language instance whose bytecode roots should stop reporting
     * @param tracer the tracer to remove
     * @throws IllegalArgumentException if {@code tracer} is exclusive to a different
     *             {@link BytecodeDescriptor}
     * @throws UnsupportedOperationException if instruction tracing is not enabled for this
     *             descriptor
     * @since 25.1
     */
    public void removeInstructionTracer(L language, InstructionTracer tracer) {
        Objects.requireNonNull(language);
        Objects.requireNonNull(tracer);
        getDescriptorData(language).removeInstructionTracer(tracer);
    }

    /**
     * Updates the bytecode configuration for all bytecode root nodes that were created by this
     * descriptor for the given language instance.
     * <p>
     * This can be used to enable features such as source information or instrumentation globally.
     * Options are applied cumulatively and cannot be reverted for that language instance.
     *
     * @param language the language instance
     * @param config the configuration to apply
     * @since 25.1
     */
    public void update(L language, BytecodeConfig config) {
        BytecodeEngineData.get(language).getDescriptor(this).updateConfig(config);
    }

    /**
     * Returns a short string representation for debugging.
     *
     * @return a concise descriptor string
     * @since 25.1
     */
    @Override
    public String toString() {
        return "BytecodeDescriptor[" + getGeneratedClass().getSimpleName() + "]";
    }

    /**
     * Returns a human readable dump of this bytecode descriptor.
     * <p>
     * The dump includes the instruction set, statistics such as encoded size, and formatted
     * instruction listings. This is intended for diagnostics and test assertions, not for
     * programmatic parsing.
     *
     * @return a multi line string describing the bytecode descriptor
     * @since 25.1
     */
    public String dump() {
        List<InstructionDescriptor> descriptors = getInstructionDescriptors();
        int maxLabelSize = Math.min(80, descriptors.stream().mapToInt((i) -> InstructionDescriptor.formatLabel(i).length()).max().orElse(0));
        String instructionsDump = formatList(descriptors,
                        (i) -> InstructionDescriptor.format(descriptors.indexOf(i), i, maxLabelSize));

        IntSummaryStatistics stats = descriptors.stream().mapToInt((i) -> i.getLength()).summaryStatistics();
        String header = String.format("%s(root=%s)[",
                        getClass().getSimpleName(),
                        getSpecificationClass() == null ? "null" : getSpecificationClass().getSimpleName());

        StringBuilder b = new StringBuilder();
        b.append(header);
        b.append("\n    instructions(" + formatStatistics(stats) + ") = " + instructionsDump);
        b.append("\n]");
        return b.toString();
    }

    private static String formatStatistics(IntSummaryStatistics stats) {
        return String.format(
                        "count=%d, sum=%db, min=%db, average=%fb, max=%db",
                        stats.getCount(),
                        stats.getSum(),
                        stats.getMin(),
                        stats.getAverage(),
                        stats.getMax());
    }

    private static <T> String formatList(List<T> list, Function<T, String> toString) {
        if (list == null) {
            return "Not Available";
        } else if (list.isEmpty()) {
            return "Empty";
        }
        StringBuilder b = new StringBuilder();
        for (T o : list) {
            b.append("\n        ");
            String v;
            try {
                v = toString.apply(o);
            } catch (Throwable t) {
                v = t.toString();
            }
            b.append(v);
        }
        return b.toString();
    }

    /**
     * Internal method called by generated code on root node load.
     *
     * @since 25.1
     */
    protected final void onPrepareForLoad(TruffleLanguage<?> language, R rootNode) {
        if (isDescriptorLookupEnabled(language)) {
            /*
             * Fast-path optimization. We want to avoid looking up the engine data if not needed.
             * Typically only needed for tracing and other debug features.
             */
            getDescriptorData(language).onPrepareForCall(rootNode);
        }
    }

    /**
     * Internal method called by generated code whenever the a {@link BytecodeConfig} is used.
     *
     * @since 25.1
     */
    protected final long withGlobalConfig(TruffleLanguage<?> language, long config) {
        if (isDescriptorLookupEnabled(language)) {
            /*
             * Fast-path optimization. We want to avoid looking up the engine data if not needed.
             * Typically only needed for tracing and other debug features.
             */
            return getDescriptorData(language).updateBytecodeConfig(config);
        }
        return config;
    }

    private DescriptorData getDescriptorData(TruffleLanguage<?> language) {
        return BytecodeEngineData.get(language).getDescriptor(this);
    }

    private boolean isDescriptorLookupEnabled(TruffleLanguage<?> language) {
        return language != null && (descriptorLookupEnabled || engineDescriptorLookupEnabled);
    }

    /**
     * We only create BytecodeEngineData if any tracer factories are installed. So we need to enable
     * it here such that the BytecodeEngineData gets created so engine instruction tracers are
     * actually created.
     */
    static final void enableEngineDescriptorLookup() {
        engineDescriptorLookupEnabled = true;
    }

    /**
     * The descriptor lookup may be enabled the first time instruction tracers are added to the
     * language using {@link #addInstructionTracer(TruffleLanguage, InstructionTracer)} or global
     * configuration updates are performed with {@link #update(TruffleLanguage, BytecodeConfig)}.
     */
    final void enableDescriptorLookup() {
        descriptorLookupEnabled = true;
    }

}
